
package UI;

import javax.media.opengl.GL2;
import javax.media.opengl.GLAutoDrawable;
import javax.media.opengl.GLEventListener;
import javax.media.opengl.GLProfile;
import javax.media.opengl.awt.GLCanvas;
import javax.media.opengl.GLCapabilities;

import com.jogamp.newt.Window;
import com.jogamp.newt.event.KeyAdapter;
import com.jogamp.newt.event.KeyEvent;
import com.jogamp.newt.event.KeyListener;
import com.jogamp.newt.event.MouseAdapter;
import com.jogamp.newt.event.MouseEvent;
import com.jogamp.newt.event.MouseListener;
import com.jogamp.newt.event.awt.AWTKeyAdapter;
import com.jogamp.newt.event.awt.AWTMouseAdapter;
import com.jogamp.opengl.util.Animator;
import com.jogamp.opengl.util.awt.TextRenderer;

import java.awt.Font;
import java.awt.geom.Rectangle2D;

import vacworld.Direction;
import vacworld.VacuumState;
import vacworld.VacuumWorld;
import agent.Action;
import agent.Percept;

class rendered_text{
	  public int textWidth;
	  public int textHeight;
	  
	  public TextRenderer renderer;
	  String text;
	  public rendered_text(TextRenderer rt){
		  renderer = rt;
	  }
	  
	  public void set_text(String text){
		  this.text = text;
		  Rectangle2D textBounds = renderer.getBounds(text);
		  textWidth = (int) textBounds.getWidth();
		  textHeight = (int) textBounds.getHeight();
	  }
	  
	  public void draw(GLAutoDrawable drawable, int pos_x, int pos_y){
			//render agent's current text information
		    renderer.beginRendering(drawable.getWidth(), drawable.getHeight());
		    // Draw text
		    renderer.draw(text, pos_x, pos_y);
		    // Clean up rendering
		    renderer.endRendering();
	  }
};
/**
 * This file is created to provide UI support for the VacuumWorld.
 * Adapted from the Gears.java that is created by Ron Cemer and Sven Gothel, 
 * who firstly convert from the Gears openGL program written by Brian Paul.<BR>
 * @author - Chao Wang 
 * contact info: chw412@lehigh.edu
 */

public class VacuumWorldUI implements GLEventListener {
  private int square=0;
  private int obstacle=0;
  private int dirt=0;
  private int agent=0;
  private float length = 30.0f;
  private float border = 3.0f;
  
  private int swapInterval;
  private rendered_text rt;

  private VacuumWorld world = null;
  private VacuumState state = null;
  private Action action = null;
  private Percept percept = null;
  boolean score_panel = false;
  float score_delta = 0.0f;
  
  int prevMouseX = 0;
  int prevMouseY = 0;
  boolean mouseRButtonDown = false;
  
  public void start_UI(VacuumWorld world, VacuumState state) {
	this.world = world;
	this.state = state;

    java.awt.Frame frame = new java.awt.Frame("The Vacuum World");
    frame.setSize(800, 700);
    frame.setAlwaysOnTop(true);
    frame.setLayout(new java.awt.BorderLayout());

    final Animator animator = new Animator();
    frame.addWindowListener(new java.awt.event.WindowAdapter() {
        public void windowClosing(java.awt.event.WindowEvent e) {
          // Run this on another thread than the AWT event queue to
          // make sure the call to Animator.stop() completes before
          // exiting
          new Thread(new Runnable() {
              public void run() {
                animator.stop();
                System.exit(0);
              }
            }).start();
        }
      });

    GLCapabilities caps = new GLCapabilities(null);
    caps.setAlphaBits(8);
    GLCanvas canvas = new GLCanvas(caps);
    animator.add(canvas);
    GLCanvas score_canvas = new GLCanvas(caps);
    animator.add(score_canvas);
    
    canvas.addGLEventListener(this);
    score_canvas.addGLEventListener(this);
    
    frame.add(canvas, java.awt.BorderLayout.CENTER);
    frame.add(score_canvas, java.awt.BorderLayout.CENTER);
    frame.validate();

    frame.setVisible(true);
    animator.start();
  }
  
  public VacuumWorldUI(int swapInterval) {
    this.swapInterval = swapInterval;
  }

  public VacuumWorldUI() {
    this.swapInterval = 1;
  }

  public void update_action(Action action){
	  this.action = action;
  }
  
  public void update_percept(Percept percept){
	  this.percept = percept;
  }
  
  public void init(GLAutoDrawable drawable) {
    System.err.println("VacuumWorldUI Init: " + drawable);
    GL2 gl = drawable.getGL().getGL2();

    float pos[] = { 5.0f, 5.0f, 10.0f, 0.0f };
    float green[] = { 0.0f, 0.8f, 0.2f, 0.7f };
    float blue[] = { 0.2f, 0.2f, 1.0f, 0.7f };
    float firebrick[] = {0.698f, 0.133f, 0.133f, 0,7f};
    float DimGray[] = {0.412f, 0.412f, 0.412f, 0.7f};
    
    gl.glLightfv(GL2.GL_LIGHT0, GL2.GL_POSITION, pos, 0);
    gl.glEnable(GL2.GL_CULL_FACE);
    gl.glEnable(GL2.GL_LIGHTING);
    gl.glEnable(GL2.GL_LIGHT0);
    gl.glEnable(GL2.GL_DEPTH_TEST);
    
    if(0>=square) {
        square = gl.glGenLists(1);
        gl.glNewList(square, GL2.GL_COMPILE);
        gl.glMaterialfv(GL2.GL_FRONT, GL2.GL_AMBIENT_AND_DIFFUSE, green, 0);
        square(gl, length);
        gl.glEndList();
        System.err.println("square list created: "+ square);
    } else {
        System.err.println("square list reused: "+square);
    }

    if(0>=dirt) {
        dirt = gl.glGenLists(1);
        gl.glNewList(dirt, GL2.GL_COMPILE);
        gl.glMaterialfv(GL2.GL_FRONT, GL2.GL_AMBIENT_AND_DIFFUSE, DimGray, 0);
        square(gl, length);
        gl.glEndList();
        System.err.println("dirt list created: "+ dirt);
    } else {
        System.err.println("dirt list reused: "+ dirt);
    }
    
    if(0>=obstacle) {
        obstacle = gl.glGenLists(1);
        gl.glNewList(obstacle, GL2.GL_COMPILE);
        gl.glMaterialfv(GL2.GL_FRONT, GL2.GL_AMBIENT_AND_DIFFUSE, firebrick, 0);
        square(gl, length);
        gl.glEndList();
        System.err.println("obstacle list created: "+ obstacle);
    } else {
        System.err.println("obstacle list reused: "+ obstacle);
    }

    if(0>=agent) {
        agent = gl.glGenLists(1);
        gl.glNewList(agent, GL2.GL_COMPILE);
        gl.glMaterialfv(GL2.GL_FRONT, GL2.GL_AMBIENT_AND_DIFFUSE, blue, 0);
        agent(gl);
        gl.glEndList();
        System.err.println("agent list created: "+ agent);
    } else {
        System.err.println("agent list reused: "+ agent);
    }
    
    gl.glEnable(GL2.GL_NORMALIZE);
    
    rt = new rendered_text(new TextRenderer(new Font("Courier New", Font.PLAIN, 18)));
    
    // MouseListener gearsMouse = new TraceMouseAdapter(new GearsMouseAdapter());
    MouseListener gearsMouse = new VacuumWorldMouseAdapter();    
    KeyListener gearsKeys = new VacuumWorldKeyAdapter();

    if (drawable instanceof Window) {
        Window window = (Window) drawable;
        window.addMouseListener(gearsMouse);
        window.addKeyListener(gearsKeys);
    } else if (GLProfile.isAWTAvailable() && drawable instanceof java.awt.Component) {
        java.awt.Component comp = (java.awt.Component) drawable;
        new AWTMouseAdapter(gearsMouse).addTo(comp);
        new AWTKeyAdapter(gearsKeys).addTo(comp);
    }
  }
    
  public void reshape(GLAutoDrawable drawable, int x, int y, int width, int height) {
    System.err.println("Reshape "+x+"/"+y+" "+width+"x"+height);
    GL2 gl = drawable.getGL().getGL2();
    gl.setSwapInterval(swapInterval);
	if(width<height)
	{
		gl.glViewport(0,0,(int)width,height);
	}
	else
	{
		gl.glViewport(0,0,(int)width,height);
	}
	gl.glMatrixMode(GL2.GL_PROJECTION);
	gl.glLoadIdentity();
    gl.glOrtho(0,360,0,360,-1000.0,1000.0);
	gl.glMatrixMode(GL2.GL_MODELVIEW);
	gl.glLoadIdentity();
  }

  public void dispose(GLAutoDrawable drawable) {
    System.err.println("Dispose");
  }

  public void display(GLAutoDrawable drawable) {
    // Get the GL corresponding to the drawable we are animating
    GL2 gl = drawable.getGL().getGL2();
    gl.glClearColor(0.0f, 0.0f, 0.0f, 0.0f);
    // Special handling for the case where the GLJPanel is translucent
    // and wants to be composited with other Java 2D content
		if (GLProfile.isAWTAvailable()
				&& (drawable instanceof javax.media.opengl.awt.GLJPanel)
				&& !((javax.media.opengl.awt.GLJPanel) drawable).isOpaque()
				&& ((javax.media.opengl.awt.GLJPanel) drawable)
						.shouldPreserveColorBufferIfTranslucent()) {
			gl.glClear(GL2.GL_DEPTH_BUFFER_BIT);
		} else {
			gl.glClear(GL2.GL_COLOR_BUFFER_BIT | GL2.GL_DEPTH_BUFFER_BIT);
		}

		gl.glPushMatrix();
		gl.glTranslatef( -score_delta+length/2, length/2, 0.0f);
				
		// draw the map
		for (int i = 0; i < state.getHeight(); ++i) {
			for (int j = 0; j < state.getWidth(); ++j) {
				gl.glPushMatrix();
				gl.glTranslatef(j * (length + border), i * (length + border),
						0.0f);
				if (state.hasObstacle(j, state.getHeight() - 1 - i)) {
					gl.glCallList(obstacle);
				} else if (state.hasDirt(j, state.getHeight() - 1 - i)) {
					gl.glCallList(dirt);
				} else {
					gl.glCallList(square);
				}
				gl.glPopMatrix();

				// draw the agent if agent is at this position
				gl.glPushMatrix();
				gl.glTranslatef(j * (length + border) + (length / 2), 
						i * (length + border) + (length / 2), 0.0f);
				if (state.getAgentX() == j && state.getAgentY() == state.getHeight() - 1 - i) {
					switch (state.getAgentDir()) {
					case Direction.NORTH:
						gl.glRotatef(90, 0, 0, 1.0f);
						break;
					case Direction.EAST:
						gl.glRotatef(0, 0, 0, 1.0f);
						break;
					case Direction.SOUTH:
						gl.glRotatef(-90, 0, 0, 1.0f);
						break;
					case Direction.WEST:
						gl.glRotatef(180, 0, 0, 1.0f);
						break;
					}
					gl.glCallList(agent);
				}
				gl.glPopMatrix();
			}
		}
		
		rt.set_text("Welcome to the Vacuum World!");
		rt.draw(drawable, 0, drawable.getHeight()-40);
	    		
		if(state.isRobotOff()){
			rt.set_text("ROBOT TURNED OFF!");
			rt.draw(drawable, 0, drawable.getHeight()-80);
		}
		
		String info = "Location: (" + state.getAgentX() + "," + state.getAgentY() + ")  ";
		rt.set_text(info);
		rt.draw(drawable, drawable.getWidth()/2, drawable.getHeight()-40);	
		
	    info = "Facing: " + state.getAgentDirString();
		rt.set_text(info);
		rt.draw(drawable, drawable.getWidth()*3/4, drawable.getHeight()-40);	
		
		if(percept != null){
			info = "Percept: " + percept.toString();
			rt.set_text(info);
			rt.draw(drawable, drawable.getWidth()/2, drawable.getHeight()-80);			
		}
		if(action != null){
			info = "Action: " + action.toString();
			rt.set_text(info);
			rt.draw(drawable, drawable.getWidth()*3/4, drawable.getHeight()-80);
		}
		
		showScore(drawable);
	    gl.glPopMatrix();
  }

  void showScore(GLAutoDrawable drawable){		
		String info = "  Base Score:\t 1000";
		rt.set_text(info);
		rt.draw(drawable, drawable.getWidth()*7/10, drawable.getHeight()*3/4);	
		
	    if (world.getNumActions()>= 200){
			rt.set_text("  Loop penalty: -100");
			rt.draw(drawable, drawable.getWidth()*6/10, drawable.getHeight()*3/4-60);
	    }
	    
		info = "  Dirt left: \t" + world.getDirtScore() + "\t(" + world.getNumDirtyLocs() + ")";
	    rt.set_text(info);
		rt.draw(drawable, drawable.getWidth()*7/10, drawable.getHeight()*3/4 - 120);
	    
		info = "  Bumps: \t" + world.getBumpsScore() + "\t(" + world.numBumps + ")"; 
	    rt.set_text(info);
		rt.draw(drawable, drawable.getWidth()*7/10, drawable.getHeight()*3/4-180);
		
		info = "  Moves: \t" + world.getMovesScore() + "\t(" + world.numMoves + ")"; 
	    rt.set_text(info);
		rt.draw(drawable, drawable.getWidth()*7/10, drawable.getHeight()*3/4-240);	

		info = "  Turns: \t" + world.getTurnsScore() + "\t(" + world.numTurns + ")";
		rt.set_text(info);
		rt.draw(drawable, drawable.getWidth()*7/10, drawable.getHeight()*3/4-300);
		
		info = "  Sucks: \t" + world.getSucksScore() + "\t(" + world.numSucks + ")"; 
	    rt.set_text(info);
		rt.draw(drawable, drawable.getWidth()*7/10, drawable.getHeight()*3/4-360);
		
		info = "  TOTAL SCORE: " + world.getPerformanceMeasure(); 
	    rt.set_text(info);
		rt.draw(drawable, drawable.getWidth()*7/10, drawable.getHeight()*3/4-420);		
  }
  /**
   * this function construct each square
   * @param gl
   * @param length
   */
  public static void square(GL2 gl, float length)
  {
	gl.glBegin(GL2.GL_POLYGON);
    gl.glTexCoord2f(0,0);
    gl.glVertex3d(0,0,0);
    gl.glTexCoord2f(1,0);
    gl.glVertex3d(length,0,0);
    gl.glTexCoord2f(1,1);
    gl.glVertex3d(length,length,0);
    gl.glTexCoord2f(0,1);
    gl.glVertex3d(0,length,0);
    gl.glEnd();
  }
  
  /**
   * this function construct the agent.
   * @param gl
   */
  public static void agent(GL2 gl){
		gl.glBegin(GL2.GL_POLYGON);
		gl.glVertex3f(12,0,3);
		gl.glVertex3f(0,12,3);
		gl.glVertex3f(0,6,3);
		gl.glVertex3f(-12,6,3);
		gl.glVertex3f(-12,-6,3);
		gl.glVertex3f(0,-6,3);
		gl.glVertex3f(0,-12,3);
		gl.glEnd();
  }

class VacuumWorldKeyAdapter extends KeyAdapter {      
    public void keyPressed(KeyEvent e) {
        int kc = e.getKeyCode();
        if(kc == KeyEvent.VK_LEFT){
        	score_delta-=5;
        }else if(kc == KeyEvent.VK_RIGHT){
        	score_delta+=5;
        }
    }
  }

class VacuumWorldMouseAdapter extends MouseAdapter {
    public void mousePressed(MouseEvent e) {
      prevMouseX = e.getX();
      prevMouseY = e.getY();
      if ((e.getModifiers() & e.BUTTON3_MASK) != 0) {
        mouseRButtonDown = true;
      }
    }
      
    public void mouseReleased(MouseEvent e) {
      if ((e.getModifiers() & e.BUTTON3_MASK) != 0) {
        mouseRButtonDown = false;
      }
    }
      
    public void mouseDragged(MouseEvent e) {
      int x = e.getX();
      int y = e.getY();
      int width=0, height=0;
      Object source = e.getSource();
      if(source instanceof Window) {
          Window window = (Window) source;
          width=window.getWidth();
          height=window.getHeight();
      } else if (GLProfile.isAWTAvailable() && source instanceof java.awt.Component) {
          java.awt.Component comp = (java.awt.Component) source;
          width=comp.getWidth();
          height=comp.getHeight();
      } else {
          throw new RuntimeException("Event source neither Window nor Component: "+source);
      }
      
      prevMouseX = x;
      prevMouseY = y;
    }
}
}
